/**
Copyright 2010, David Keyes.  All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may 
not use this file except in compliance with the License. You may obtain
a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an "AS IS" BASIS, 
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and 
limitations under the License. 

*/
package com.keyes.license;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.widget.Button;

import com.keyes.license.CheckLicenseCallback;
import com.keyes.license.api.model.AppsRequest;
import com.keyes.license.api.model.Request;
import com.keyes.license.api.model.RequestContext;
import com.keyes.license.api.model.Response;
import com.keyes.protobuf.CodedInputStream;

public class LicenseManagerImpl {
  

  private static final String PREFS_KEY_FAILURE_COUNT = "com.keyes.license.failureCount";
  private static final String PREFS_KEY_LAST_PURCHASE_CHECK_TIME = "com.keyes.license.lastLicenseCheckTime";
  private static final long   PREFS_LAST_PURCHASE_CHECK_DELAY = 30000;
  private static final String PREFS_KEY_LICENSE = "com.keyes.license";
  private static final String PREFS_KEY_LICENSE_TIMEOUT = "com.keyes.license.timeout";
  private static final long   PREFS_LICENSE_TIMEOUT = 30 * 60 * 60 * 1000;
  private static final String PREFS_KEY_INITIAL_WELCOME_DISPLAYED = "com.keyes.license.welcome";

  public static final String COM_KEYES_LICENSE_PREFS = "com.keyes.license.prefs";
  
  private static final int SOCKET_TIMEOUT = 5000;
  private static final int READ_TIMEOUT = 8000;
  
  private static final int PROTOCOL_VERSION = 2;
  
  private static final String SERVICE = "android";

  private static final String URL_LOGIN = "https://www.google.com/accounts/ClientLogin";
  private static final String ACCOUNT_TYPE_GOOGLE = "GOOGLE";
  private static final String ACCOUNT_TYPE_HOSTED = "HOSTED";
  private static final String ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE";

  private static long sLicensedChecked = 0L;
  
  protected static ValidateWithMarketAsyncTask sTask = null;
  
  private static LicenseManager.LicensePolicy sLicensePolicy = new LicenseManager.LicensePolicy();

  private static String[] sCrackMsg = {"VGhpcyBhcHAgaGFzIGJlZW4gY3JhY2tlZCE="};
  
  static {
    Handler lHandler = new Handler();
    
    lHandler.postDelayed(new Runnable(){

      @Override
      public void run() {
        long lT = 100L / sLicensedChecked;
        lT ++;
      }
      
    }, 20000);

  }
  
  private LicenseManagerImpl(){
  }
  
  /**
   * Set the policy for handling of failures.
   * 
   * @param pMaxFailures Maximum number of failures before the failure policy is applied.
   * @param pFailurePolicy Behavior when maximum number of failures is exceeded.
   * @param pFailureDelay The deilay to force on the user before allowing them to exit the failure notification screen.
   */
  protected static void setValidationPolicy(
      int pMaxFailures,
      int pFailurePolicy,
      int pFailureDelay
  ){
    sLicensePolicy = new LicenseManager.LicensePolicy();
    sLicensePolicy.maxFailures = pMaxFailures;
    sLicensePolicy.failureBehavior = pFailurePolicy;
    sLicensePolicy.failureDelay = pFailureDelay;
  }
  
  private static Response executeRawHttpQuery(Request pRequest) {
    try {
      
      URL url = new URL("https://android.clients.google.com/market/api/ApiRequest");
      HttpsURLConnection lConn = (HttpsURLConnection)url.openConnection();
      lConn.setHostnameVerifier(new NullHostnameVerifier());

      lConn.setConnectTimeout(SOCKET_TIMEOUT);
      lConn.setReadTimeout(READ_TIMEOUT);
      lConn.setRequestMethod("POST");
      lConn.setDoOutput(true);
      lConn.setRequestProperty("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
      lConn.setRequestProperty("Cookie","ANDROID="+pRequest.getRequestContext().getAuthSubToken());
      lConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
      
      // sniffed this from the market application on the G1... does this need to match what we send in
      // the actual API request???
      lConn.setRequestProperty("User-Agent", "Android-Market/2 (sapphire PLAT-RC33); gzip");
      
      String request64 = pRequest.toBase64String();
      
      String lReqData = "version="+PROTOCOL_VERSION;
      lReqData += "&request="+request64;
      
      lConn.setFixedLengthStreamingMode(lReqData.getBytes("UTF-8").length);
      OutputStream lOutputStream = lConn.getOutputStream();
      lOutputStream.write(lReqData.getBytes());
      lOutputStream.close();
      
      if(lConn.getResponseCode() >= 400) {
        throw new LicenseException("" + lConn.getResponseCode() + 
            " - HTTP Response: " + lConn.getResponseMessage());
      }
      
      InputStream lInputStream = lConn.getInputStream();
      GZIPInputStream lGZipInputStream = new GZIPInputStream(lInputStream);
      ByteArrayOutputStream lByteArrayOutputStream = new ByteArrayOutputStream();
      byte[] lBuff = new byte[1024];
      while(true) {
        int lByteCount = lGZipInputStream.read(lBuff);
        if(lByteCount < 0){
          break;
        }
        lByteArrayOutputStream.write(lBuff,0,lByteCount);
      }
      lInputStream.close();
      lConn.disconnect();

      
      CodedInputStream lCos = CodedInputStream.newInstance(lByteArrayOutputStream.toByteArray());
      Response lRet = new Response(lCos);
      lRet.parse();
      
      return lRet;
      
    } catch(Exception ex) {
      throw new LicenseException(ex);
    }
  }

  private static void storeKey(SharedPreferences pPrefs, String pAndroidId, String pPackageName) {
    
    String lL = createKey(pAndroidId, pPackageName);
    
    long lLicTimeout = pPrefs.getLong(PREFS_KEY_LICENSE_TIMEOUT, -1);
    
    Editor lEdit = pPrefs.edit();
    lEdit.putString(PREFS_KEY_LICENSE, lL);
    lEdit.putInt(PREFS_KEY_FAILURE_COUNT, 0);
    if(lLicTimeout == -1){
      lEdit.putLong(PREFS_KEY_LICENSE_TIMEOUT, System.currentTimeMillis());
    } else {
      lEdit.putLong(PREFS_KEY_LICENSE_TIMEOUT, 0);
    }
    lEdit.commit();

  }
  private static String createKey(String pAndroidId, String pPackageName) {

    String lStr = pAndroidId + "Y29tLmtleWVzLmxpY2Vuc2luZw==" + pPackageName + pAndroidId; 
    BigInteger i=null;    
    try {
      MessageDigest m = MessageDigest.getInstance("MD5");
      byte[] data = lStr.getBytes(); 
      m.update(data,0,data.length);
      i = new BigInteger(1,m.digest());

    } catch(NoSuchAlgorithmException e) {
      Log.e(LicenseManagerImpl.class.getSimpleName(), "Error generating license key.", e);
    }
    
    return String.format("%1$032X", i);
    
  }

  private static boolean validatePurchaseAgainstAppStore(
      String pPackageName, 
      String pAuthToken,
      String pAndroidId,
      String pCountry, 
      String pLanguage,
      String pOperatorAlpha,
      String pOperatorNumeric,
      String pSimOperatorAlpha,
      String pSimOperatorNumeric,
      String pDeviceAndSdkVersion
      ){
    
    Request lRequest = new Request();
    RequestContext lReqContext = new RequestContext();
    
    lReqContext.setAndroidId(pAndroidId);
    lReqContext.setAuthSubToken(pAuthToken);

    lReqContext.setOperatorAlpha(pOperatorAlpha);
    lReqContext.setSimOperatorAlpha(pSimOperatorAlpha);
    lReqContext.setOperatorNumeric(pOperatorNumeric);
    lReqContext.setSimOperatorNumeric(pSimOperatorNumeric);

    lReqContext.setDeviceAndSdkVersion(pDeviceAndSdkVersion);
    //lReqContext.setDeviceAndSdkVersion("sapphire:7");
    lReqContext.setUserCountry(pCountry);
    lReqContext.setUserLanguage(pLanguage);

    AppsRequest lAppsRequest = new AppsRequest();
    lAppsRequest.setQuery("pname:"+pPackageName);
    lAppsRequest.setStartIndex(0);
    lAppsRequest.setEntriesCount(10);
    lAppsRequest.setWithExtendedInfo(1);
    
    lRequest.setRequestContext(lReqContext);
    lRequest.setAppsRequest(lAppsRequest);
    
    Response lResp = executeRawHttpQuery(lRequest);
    
    if(lResp == null){
      return false;
    }
    
    if(lResp.getAppsResponse() == null){
      return false;
    }

    if(lResp.getAppsResponse().getApp() == null){
      return false;
    }

    return lResp.getAppsResponse().getApp().isPurchased();
  }
  

  /**
   * Host name verifier that does not perform nay checks.
   */
  private static class NullHostnameVerifier implements HostnameVerifier {
      public boolean verify(String hostname, SSLSession session) {
          return true;
      }
  }  
 
  protected synchronized static void setTaskNull(){
    sTask = null;
  }
  
  private static String getAuthToken(Activity pActivity, boolean pDisplayUi){
    String lAuthToken = null;
    
    try {
      Class lAccMgrClass = Class.forName("android.accounts.AccountManager");
      Method lGetMethod = lAccMgrClass.getDeclaredMethod("get", Context.class);
      Method lGetAcctsByTypeMethod = lAccMgrClass.getDeclaredMethod("getAccountsByType", String.class);
      Object lAm = lGetMethod.invoke(null, pActivity);
      Object lAccts = lGetAcctsByTypeMethod.invoke(lAm, "com.google");
  
      if(lAccts == null || Array.getLength(lAccts) == 0){
        return null;
      }
      
      Class lAccClass = Class.forName("android.accounts.Account");
      Class lAccMgrCallbackClass = Class.forName("android.accounts.AccountManagerCallback");
      Method lGetAuthTokenMethod = lAccMgrClass.getDeclaredMethod("getAuthToken", lAccClass, String.class, Bundle.class, Activity.class, lAccMgrCallbackClass, Handler.class );
      Object lAmF = lGetAuthTokenMethod.invoke(lAm, Array.get(lAccts, 0), "android", null, pActivity, null, null);
        
      Bundle lAuthResult;
      
      Class  lAccMgrFutureClass = Class.forName("android.accounts.AccountManagerFuture");
      Method lGetResultMethod = lAccMgrFutureClass.getDeclaredMethod("getResult", long.class, TimeUnit.class);
      lAuthResult = (Bundle) lGetResultMethod.invoke(lAmF, (long) 10, TimeUnit.SECONDS);
   
      Intent lAuthIntent = (Intent) lAuthResult.get("intent");
      
      if(lAuthIntent != null && pDisplayUi){
        pActivity.startActivity(lAuthIntent);
        return null;
      }
  
      lAuthToken   = (String) lAuthResult.get("authtoken");
    } catch(ClassNotFoundException e) {
      Log.d(LicenseManagerImpl.class.getSimpleName(), "Error retrieving AuthToken", e);
      
    } catch (IllegalArgumentException e) {
      Log.d(LicenseManagerImpl.class.getSimpleName(), "Error retrieving AuthToken", e);
    } catch (IllegalAccessException e) {
      Log.d(LicenseManagerImpl.class.getSimpleName(), "Error retrieving AuthToken", e);
    } catch (InvocationTargetException e) {
      Log.d(LicenseManagerImpl.class.getSimpleName(), "Error retrieving AuthToken", e);
    } catch (SecurityException e) {
      Log.d(LicenseManagerImpl.class.getSimpleName(), "Error retrieving AuthToken", e);
    } catch (NoSuchMethodException e) {
      Log.d(LicenseManagerImpl.class.getSimpleName(), "Error retrieving AuthToken", e);
    }
    
    return lAuthToken;
  }
  
  
  /**
   * @param pActivity
   */
  protected synchronized static void checkLicense(Activity pActivity){
    checkLicense(pActivity, true);
    return;
  }
  
  /**
   * @param pActivity
   * @param pCallback
   */
  protected synchronized static void checkLicense(Activity pActivity, CheckLicenseCallback pCallback){
    checkLicense(pActivity, true, pCallback);
    return;
  }  
  
  /**
   * @param pAppLicense
   * @param pActivity
   * @param pDisplayUI
   */
  protected synchronized static void checkLicense(Activity pActivity, boolean pDisplayUI){
    checkLicense(pActivity, pDisplayUI, null);
    return;
  }    
  
  /**
   * @param pActivity
   * @param pDisplayUI
   * @param pCallback
   */
  protected synchronized static void checkLicense(final Activity pActivity, final boolean pDisplayUI, final CheckLicenseCallback pCallback){

    /////////////////
    // mark license as checked
    sLicensedChecked = System.currentTimeMillis();
    
    if( ! checkCompatibleAndroidVersion() ){
      if(pCallback != null){
        pCallback.licenseResult(LicenseManager.LICENSE_STATE_UNKNOWN);
      }
      return;
    }
    
    if( ! checkValidApplicationLicense("" /* removed licensing check */, pActivity.getPackageName()) ){
      Builder lBuilder = new AlertDialog.Builder(pActivity);
      lBuilder.setTitle("License Manager DEMO Mode!");
      lBuilder.setMessage("You're using a demo version of the LicenseManager which is not licensed for ["+pActivity.getPackageName()+"].  Please feel free to poke around and evaluate but be sure to purchase a license from keyeslabs.com before deploying to the Android Market.");
      lBuilder.setCancelable(false);
      
      lBuilder.setNeutralButton("OK", new DialogInterface.OnClickListener(){

        @Override
        public void onClick(DialogInterface pDialog, int pWhich) {
          pDialog.dismiss();
          performLicenseWizard(pActivity, pDisplayUI, pCallback);
        }
        
      });
      
      lBuilder.show();
    } 
    
    // if we ARE licensed...
    else {
      performLicenseWizard(pActivity, pDisplayUI, pCallback);
    }
    
    
  }
  
  protected static void setMessages(Bundle pMessagesBundle){
    
    if(pMessagesBundle == null){
      return;
    }
    
    LicenseManager.mMsgDialogTitle_Intro = setMessage(pMessagesBundle.getString("message.dialog.intro.title"), LicenseManager.mMsgDialogTitle_Intro);
    LicenseManager.mMsgDialogBody_Intro = setMessage(pMessagesBundle.getString("message.dialog.intro.body"), LicenseManager.mMsgDialogBody_Intro);
    LicenseManager.mMsgDialogButton_Intro_Cancel = setMessage(pMessagesBundle.getString("message.dialog.intro.button.cancel"), LicenseManager.mMsgDialogButton_Intro_Cancel);
    LicenseManager.mMsgDialogButton_Intro_Next = setMessage(pMessagesBundle.getString("message.dialog.intro.button.next"), LicenseManager.mMsgDialogButton_Intro_Next);

    LicenseManager.mMsgDialogTitle_LicenseCheck = setMessage("message.dialog.licenseCheck.title", LicenseManager.mMsgDialogTitle_LicenseCheck);
    LicenseManager.mMsgDialogStatus_Authenticate = setMessage("message.dialog.licenseCheck.status.authenticate", LicenseManager.mMsgDialogStatus_Authenticate);
    LicenseManager.mMsgDialogStatus_ValidatePurchase = setMessage("message.dialog.licenseCheck.status.validatePurchase", LicenseManager.mMsgDialogStatus_ValidatePurchase);
    LicenseManager.mMsgDialogStatus_InstallingLicense = setMessage("message.dialog.licenseCheck.status.installingLicense", LicenseManager.mMsgDialogStatus_InstallingLicense);
    LicenseManager.mMsgDialogStatus_Finished = setMessage("message.dialog.licenseCheck.status.finished", LicenseManager.mMsgDialogStatus_Finished);
    LicenseManager.mMsgDialogStatus_NotAuthorized = setMessage("message.dialog.licenseCheck.status.notAuthorized", LicenseManager.mMsgDialogStatus_NotAuthorized);
    
    
    LicenseManager.mMsgDialogTitle_LicenseFailure = setMessage(pMessagesBundle.getString("message.dialog.licenseFailure.title"), LicenseManager.mMsgDialogTitle_LicenseFailure);
    LicenseManager.mMsgDialogBody_LicenseFailure = setMessage(pMessagesBundle.getString("message.dialog.licenseFailure.body"), LicenseManager.mMsgDialogBody_LicenseFailure);
    LicenseManager.mMsgDialogButton_LicenseFailure_Buy = setMessage(pMessagesBundle.getString("message.dialog.licenseFailure.button.buy"), LicenseManager.mMsgDialogButton_LicenseFailure_Buy);
    LicenseManager.mMsgDialogButton_LicenseFailure_Continue = setMessage(pMessagesBundle.getString("message.dialog.licenseFailure.button.continue"), LicenseManager.mMsgDialogButton_LicenseFailure_Continue);
    LicenseManager.mMsgDialogButton_LicenseFailure_Exit = setMessage(pMessagesBundle.getString("message.dialog.licenseFailure.button.exit"), LicenseManager.mMsgDialogButton_LicenseFailure_Exit);
    

  }
  
  private static String setMessage(String pValue, String pDefault) {
    if(pValue != null){
      return pValue;
    }
    
    return pDefault;
    
  }

  private static void performLicenseWizard(final Activity pActivity, final boolean pDisplayUi, final CheckLicenseCallback pCallback){

    final SharedPreferences lPrefs = getSharedPrefs(pActivity);
    boolean lShowedWelcome = lPrefs.getBoolean(PREFS_KEY_INITIAL_WELCOME_DISPLAYED, false);
    
    if(pDisplayUi && ! lShowedWelcome){

      Builder lWizardBuilder = new AlertDialog.Builder(pActivity);
      lWizardBuilder.setTitle(LicenseManager.mMsgDialogTitle_Intro);
      lWizardBuilder.setMessage(LicenseManager.mMsgDialogBody_Intro);
      lWizardBuilder.setCancelable(false);
      
      lWizardBuilder.setPositiveButton(LicenseManager.mMsgDialogButton_Intro_Cancel, new DialogInterface.OnClickListener(){

        @Override
        public void onClick(DialogInterface pDialog, int pWhich) {
          registerFailedValidation(lPrefs);
          if(pCallback != null){
            pCallback.licenseResult(LicenseManager.LICENSE_STATE_UNKNOWN);
          }
          pDialog.dismiss();
        }
        
      });
      
      lWizardBuilder.setNegativeButton(LicenseManager.mMsgDialogButton_Intro_Next, new DialogInterface.OnClickListener(){

        @Override
        public void onClick(DialogInterface pDialog, int pWhich) {
          performLicenseCheck(pActivity, pDisplayUi, pCallback);
          pDialog.dismiss();

          Editor lEditor = lPrefs.edit();
          lEditor.putBoolean(PREFS_KEY_INITIAL_WELCOME_DISPLAYED, true);
          lEditor.commit();
        }
        
      });
      
      lWizardBuilder.show();
      

    }
    
    else {
      performLicenseCheck(pActivity, pDisplayUi, pCallback);
    }
  }

  
  private static void performLicenseCheck(final Activity pActivity, final boolean pShowUi, final CheckLicenseCallback pCallback){

    final SharedPreferences lPrefs = getSharedPrefs(pActivity);
    int lRet = LicenseManager.LICENSE_STATE_UNKNOWN;

    //////////////
    // check for existing license tied to this phone...
    
    if( ! isLicensed(pActivity) ){

      // TODO: make sure that we haven't checked too recently...
      
      if(sTask != null && sTask.getStatus() == AsyncTask.Status.RUNNING){
        
        // tell the current task to quit whatever it's doing...
        sTask.cancel(true);
        
        try {
          
          sTask.get(3, TimeUnit.SECONDS);
          
        } catch (CancellationException e) { 
        } catch (InterruptedException e) {
          Log.d(LicenseManagerImpl.class.getSimpleName(), "Validation interrupted.", e);
        } catch (ExecutionException e) {
          Log.d(LicenseManagerImpl.class.getSimpleName(), "Error validating purchase.", e);
        } catch (TimeoutException e) {
          Log.d(LicenseManagerImpl.class.getSimpleName(), "Timed out waiting for validation to complete.", e);
        }
        
      } 

      sTask = new ValidateWithMarketAsyncTask(pShowUi, new CheckLicenseCallback() {

        @Override
        public void licenseError(int pLicenseResult, LicenseException pLicenseException) {
        }

        @Override
        public void licenseResult(int pResult) {
          
          // are we over the limit for purchase validation failures?
          if( pResult == LicenseManager.LICENSE_STATE_UNKNOWN || pResult == LicenseManager.UNLICENSED ){ 
            if(maxFailuresExceeded(lPrefs) ){
              executeFailurePolicy(pActivity, pShowUi, pCallback);
            } else {
              if(pCallback != null){
                pCallback.licenseResult(pResult);
              }
            }
            

          } else {
            if(pCallback != null){
              pCallback.licenseResult(pResult);
            }
          }
          
        }

        
      });
      sTask.execute(pActivity);
    
      
    }
    
    // else we are licensed... 
    else {
      if(pCallback != null){
        pCallback.licenseResult(LicenseManager.LICENSE_CONFIRMED);
      }
    }
    

  }
  
//  public static boolean validatePurchase(Context pContext, PurchaseValidationCallback pCallback){
//    
//  }


 private static boolean checkValidApplicationLicense(String pAppLicense, String pAppName) {
   
	return true;
  }

private static boolean checkCompatibleAndroidVersion() {
    boolean lRet = true;
    
    try { 
      Class.forName("android.accounts.Account");
    } catch(Throwable t) {
      lRet = false;
    }
    
    return lRet;
  }

 private static void executeFailurePolicy(Activity pActivity, boolean pShowUi, final CheckLicenseCallback pCallback ) {

   if ((sLicensePolicy.failureBehavior & LicenseManager.FAILURE_BEHAVIOR_IGNORE) != 0) {
      if (pCallback != null) {
        pCallback.licenseResult(LicenseManager.UNLICENSED);
      }
   }

   else if ((sLicensePolicy.failureBehavior & LicenseManager.FAILURE_BEHAVIOR_INVITE) != 0) {

      final Activity lActivity = pActivity;

      Builder lBuilder = new AlertDialog.Builder(pActivity);
      lBuilder.setTitle(LicenseManager.mMsgDialogTitle_LicenseFailure);
      lBuilder.setMessage(LicenseManager.mMsgDialogBody_LicenseFailure);
      lBuilder.setCancelable(false);

      String lNegButtonTxt = null;
      if ((sLicensePolicy.failureBehavior & LicenseManager.FAILURE_BEHAVIOR_BLOCK) != 0) {
        lNegButtonTxt = LicenseManager.mMsgDialogButton_LicenseFailure_Exit;
      } else {
        lNegButtonTxt = LicenseManager.mMsgDialogButton_LicenseFailure_Continue;
      }
      lBuilder.setNegativeButton(lNegButtonTxt, new OnClickListener() {

        @Override
        public void onClick(DialogInterface pDialog, int pWhich) {
          pDialog.dismiss();

          if ((sLicensePolicy.failureBehavior & LicenseManager.FAILURE_BEHAVIOR_BLOCK) != 0) {
            lActivity.finish();
          }
          if (pCallback != null) {
            pCallback.licenseResult(LicenseManager.UNLICENSED);
          }

        }

      });

      lBuilder.setPositiveButton(
          LicenseManager.mMsgDialogButton_LicenseFailure_Buy,
          new OnClickListener() {

            @Override
            public void onClick(DialogInterface pDialog, int pWhich) {

              Intent lUpgradeIntent = new Intent(Intent.ACTION_VIEW);
              String lPackageName = lActivity.getPackageName();
              lUpgradeIntent.setData(Uri.parse("market://search?q=pname:"
                  + lPackageName));
              lUpgradeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
              lActivity.startActivity(lUpgradeIntent);

              if ((sLicensePolicy.failureBehavior & LicenseManager.FAILURE_BEHAVIOR_BLOCK) != 0) {
                lActivity.finish();
              }

              if (pCallback != null) {
                pCallback.licenseResult(LicenseManager.UNLICENSED);
              }
            }

          });

      AlertDialog lDialog = lBuilder.create();

      lDialog.show();

      // Nag the user...
      if ((sLicensePolicy.failureBehavior & LicenseManager.FAILURE_BEHAVIOR_NAG) != 0) {

        final Button lNegButton = lDialog
            .getButton(AlertDialog.BUTTON_NEGATIVE);
        lNegButton.setEnabled(false);

        Handler lHandler = new Handler();
        lHandler.postDelayed(new Runnable() {

          @Override
          public void run() {
            lNegButton.setEnabled(true);
          }

        }, sLicensePolicy.failureDelay);
      }

    }
    
    
  }

  private static boolean maxFailuresExceeded(SharedPreferences pPrefs) {

    int lNumFailures = pPrefs.getInt(PREFS_KEY_FAILURE_COUNT, 0);
    if(lNumFailures >= sLicensePolicy.maxFailures ){
      return true;
    }
    
    return false;
  }

  private static boolean isLicensed(Activity pActivity) {
    SharedPreferences lPrefs = getSharedPrefs(pActivity);
    boolean lRet = false;
    
    String lL = lPrefs.getString(PREFS_KEY_LICENSE, null);
    long   lTo = lPrefs.getLong(PREFS_KEY_LICENSE_TIMEOUT, 0);
    if(lL == null){
      return false;
    }
    
    String lPackageName = pActivity.getPackageName();
    String lAndroidId = Secure.getString(pActivity.getContentResolver(), Secure.ANDROID_ID);

    String lL2 = createKey(lAndroidId, lPackageName);
    
    lRet = lL.equals(lL2);
    
    if(lTo > 0 && System.currentTimeMillis() - lTo > PREFS_LICENSE_TIMEOUT ){
      lRet = false;
    }
    return lRet;
  }

  private static void registerFailedValidation(SharedPreferences pPrefs) {
    int lCount = pPrefs.getInt(PREFS_KEY_FAILURE_COUNT, 0);
    
    Editor lEditor = pPrefs.edit();
    lEditor.putInt(PREFS_KEY_FAILURE_COUNT, lCount+1);
    lEditor.commit();
    
  }

  
  private static SharedPreferences getSharedPrefs(Context pContext) {
    return pContext.getSharedPreferences(COM_KEYES_LICENSE_PREFS, Context.MODE_PRIVATE);
  }
  
  private static class ValidateWithMarketAsyncTask extends AsyncTask<Activity, Integer, Integer> {

    Activity       mActivity;
    ProgressDialog mPd;
    boolean        mDisplayUi = true;
    CheckLicenseCallback mCallback = null;
    
    public ValidateWithMarketAsyncTask(){
    }
    
    public ValidateWithMarketAsyncTask(boolean pDisplayUi, CheckLicenseCallback pCallback){
      mDisplayUi = pDisplayUi;
      mCallback = pCallback;
    }

    @Override
    protected Integer doInBackground(Activity... pActivity) {

      int     lRet = LicenseManager.LICENSE_STATE_UNKNOWN;
      boolean lWasPurchased = false;
      
      // we need to be VERY careful with this.  This is a static variable and we could leak 
      // lots of memory if we don't clean up after ourselves...
      mActivity = pActivity[0];
      
      try { 
        SharedPreferences lPrefs = getSharedPrefs(pActivity[0]);
        
        String lPackageName = pActivity[0].getPackageName();
        
        // update progress to inform user that we're authenticating
        this.publishProgress(1);

        String lAuthToken = getAuthToken(pActivity[0], mDisplayUi);

        String lAndroidId = Secure.getString(pActivity[0].getContentResolver(), Secure.ANDROID_ID);
        
        Locale lLocale = Locale.getDefault(); 
        String lCountry = lLocale.getCountry();
        String lLanguage = lLocale.getLanguage();
        
        TelephonyManager lTm = (TelephonyManager) pActivity[0].getSystemService(Context.TELEPHONY_SERVICE);
        String lOpAlpha = lTm.getNetworkOperatorName();
        String lOpNumeric = lTm.getNetworkOperator();
        String lSimOpAlpha = lTm.getSimOperatorName();
        String lSimOpNumeric = lTm.getSimOperator();
        
        String lDeviceAndSdkVersion = Build.DEVICE + ":" + getSdkVersion();

        // tell user that we're talking with Google
        publishProgress(2);
        
        long lLastCheckTime = lPrefs.getLong(PREFS_KEY_LAST_PURCHASE_CHECK_TIME, 0);
        if(System.currentTimeMillis() - lLastCheckTime > PREFS_LAST_PURCHASE_CHECK_DELAY){
          lWasPurchased = validatePurchaseAgainstAppStore(
              lPackageName, 
              lAuthToken, 
              lAndroidId, 
              lCountry, 
              lLanguage, 
              lOpAlpha, 
              lOpNumeric, 
              lSimOpAlpha, 
              lSimOpNumeric, 
              lDeviceAndSdkVersion
          );
          
          Editor lEditor = lPrefs.edit();
          lEditor.putLong(PREFS_KEY_LAST_PURCHASE_CHECK_TIME, System.currentTimeMillis());
          lEditor.commit();

          if( ! lWasPurchased){
            registerFailedValidation(lPrefs);
            publishProgress(5);
            Thread.sleep(2000);
            return LicenseManager.UNLICENSED;
          }

        } else {
          publishProgress(5);
          Thread.sleep(2000);
          return LicenseManager.LICENSE_STATE_UNKNOWN;
        }
        

        lRet = LicenseManager.LICENSE_CONFIRMED;
        
        /////////////////
        // generate license
        publishProgress(3);
        storeKey(lPrefs, lAndroidId, lPackageName);
        Thread.sleep(2000);
        
        publishProgress(4);
        Thread.sleep(2000);

        
      } catch(Throwable t){
        lRet = LicenseManager.LICENSE_STATE_UNKNOWN;
        Log.e(LicenseManagerImpl.class.getSimpleName(), "Error Validating Purchase", t);
      }

      return lRet;
    }

    private String getSdkVersion() {
      
      String lBuildVersStr = Build.VERSION.SDK;
      
      try {
        Field lBuildVerField = Build.VERSION.class.getDeclaredField("SDK_INT");
        lBuildVersStr = "" + lBuildVerField.getInt(null);
      } catch (Throwable t) {
        lBuildVersStr = Build.VERSION.SDK;
      }
      return lBuildVersStr;
    }

    @Override
    protected void onPostExecute(Integer pResult) {
      super.onPostExecute(pResult);
      
      try {
        
        if(mCallback != null){
          mCallback.licenseResult(pResult);
        }
        
        mActivity = null;
        if(mPd != null){
          mPd.dismiss();
        }
        mPd = null;
        
        setTaskNull();
      } catch(Throwable t){
        Log.d(LicenseManagerImpl.class.getSimpleName(), "Error cleaning up after purchase validation.", t);
      }
    }

    @Override
    protected void onProgressUpdate(Integer... pValues) {
      super.onProgressUpdate(pValues);
      
      if( ! mDisplayUi){
        return;
      }
      
      try {
        if(pValues == null  || pValues.length == 0){
          return;
        }
        
        if( isCancelled() ){
          return;
        }
        
        if(mPd == null){
          mPd = new ProgressDialog(mActivity);
          mPd.setMessage(LicenseManager.mMsgDialogStatus_Authenticate);
          mPd.setOwnerActivity(mActivity);
          mPd.setSecondaryProgress(0);
          mPd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
          mPd.setIndeterminate(false);
          mPd.setCancelable(false);
          mPd.setCanceledOnTouchOutside(false);
          mPd.setMax(4);
          mPd.show();
        }
  
        if(pValues[0] == 1){
          mPd.setProgress(1);
          mPd.setSecondaryProgress(0);
          mPd.setMessage(LicenseManager.mMsgDialogStatus_Authenticate);
        }
        
        else if(pValues[0] == 2 ){
          mPd.setProgress(2);
          mPd.setSecondaryProgress(0);
          mPd.setMessage(LicenseManager.mMsgDialogStatus_ValidatePurchase);
        }
        
        else if(pValues[0] == 3){
          mPd.setProgress(3);
          mPd.setSecondaryProgress(0);
          mPd.setMessage(LicenseManager.mMsgDialogStatus_InstallingLicense);
        }
  
        else if(pValues[0] == 4){
          mPd.setProgress(4);
          mPd.setSecondaryProgress(0);
          mPd.setMessage(LicenseManager.mMsgDialogStatus_Finished);
        }

      
        else if(pValues[0] == 5){
          mPd.setProgress(4);
          mPd.setSecondaryProgress(0);
          mPd.setMessage(LicenseManager.mMsgDialogStatus_NotAuthorized);
        }

      
      } catch(Throwable t) {
        Log.d(LicenseManagerImpl.class.getSimpleName(), "Error validating purchase.", t);
      }
    }
  }

  
}
